[Chapter 12] Class
Author: Bryan Costales & Eric Allman
Date: 1999-01-06T18:36:48Z

#

| Chapter 12 |

# 12. Class

Contents:
The Class Command
The File Form of Class
Things to Try

In rule sets, it is often advantageous to compare individual tokens to multiple strings in determining a match. For example, consider the rules developed in the last chapter, that is, the sender rewriting rules from the hub delivery agent's `S=Hubset` rule set:

>     SHubset # Rewrite the sender for the hub
>     R$-             $@ $1@${HUB}            user -> user@hub
>     R$-@$w          $@ $1@${HUB}            user@local -> user@hub

The second rule's LHS looks for any sender address that is composed of a single username followed by an `@` character and ending with the short name of the local machine (`$w`). Such an address is rewritten by the RHS to become that of the central forwarding machine, defined by the `{HUB}` macro.

Now suppose that the local machine is known by several names in addition to the short name in `$w`. All machines, for example, have a short name (such as here) and a fully qualified name (such as here.us.edu). They also often refer to themselves as localhost. In addition, some machines can play special roles at a site (such as having a printer or containing a central directory of fonts) and might have another name appropriate to that role.

To convert any sender address so that it becomes the central forwarder's name, no matter what the local host's name, you can use sendmail classes. In this chapter we will cover the class configuration command and its cousin, the file configuration command. Proper use of the class and file commands allows you to replace many rules with a single rule.

## 12.1 The Class Command

The class command declares a macro whose value is a list of strings. Rule sets may then compare the workspace to that list of strings. One such list could be a list of names by which the local machine is known.

A class is referenced in the LHS with the `$=` prefix:

>     $=X           single-character name
>     $={XXX}       multicharacter name (beginning with V8.7)

Here, `X` is a single-character class name. Beginning with V8.7 sendmail, multicharacter class name may be used, as shown in the second line above. Multicharacter class names must always be enclosed in curly braces.

The workspace is tokenized as usual; then the appropriate token is looked up to see whether it was defined as belonging to the class referenced by `$=`. If the token was found, the workspace at that point is considered to be matched. We'll cover this in more detail shortly.

### 12.1.1 Declaring a Class

The words that form the list of words in a class are declared with the `C` configuration command. The form for the class configuration command is as follows:

>     CXlist           single-character name
>     C{XXX}list       multicharacter name (beginning with V8.7)

The class configuration command starts with the letter `C`, which must begin a line. The `C` is immediately followed (with no intervening whitespace) by the name of that class. A class name can be a single ASCII character or, beginning with V8.7 sendmail, multiple ASCII characters enclosed in curly braces. A whitespace-separated `list` of word elements follows on the same line. Space between the name and the `list` is optional.

For example, the following declaration places two possible names for the local machine into the class named `w`:

>     Cw printer1 fontserver

Multiple declarations of the same class macro may exist. Each appends its word elements to the preceding list. For example, the following produces the same result as the single line above:

>     Cw printer1
>     Cw fontserver

Both examples define a class named `w`, and both assign to that class the same list of two words.

#### 12.1.1.1 A caveat

Class names and macro names are completely independent. To illustrate, consider the following two configuration commands:

>     Dwprinter1
>     Cwprinter1

Both assign the value `printer1` to the name `w`. The first assigns that value to a macro, the second to a class. Internally, sendmail stores the value `printer1` twice, first as type "macro" and second as type "class." Although they share the same value (`printer1`), macros and classes are completely independent of each other.

### 12.1.2 Multiple Known Names for the Local Host

As an example of one common use of the class macro, we will consider the case of machines that are known by multiple names. Run sendmail again:

>     % ./sendmail -Cclient.cf -bt
>     ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
>     Enter <ruleset> <address>
>     >

Now give it the special (new to V8.7 sendmail) rule-testing command `$=` (which tells sendmail to print the values stored in a class) and follow that with the letter (class name) `w`:

>     > $=w
>     here.us.edu
>     here
>     [123.45.67.8]
>     >

This illustrates that sendmail is aware of three of your machine's possible names (the last is an IP address). But recall from the previous chapter that the rule that we created looked for the local machine using only the short name in the macro `w`, not the three names in class `w`:

>     R$-@$w          $@ $1@${HUB}            user@local -> user@hub

If we didn't have class macros, we would have to look for the local machine name using a series of rules like those below:

>     R$-@$w                 $@ $1@${HUB}     user@local -> user@hub
>     R$-@here.us.edu        $@ $1@${HUB}     user@local -> user@hub
>     R$-@[123.45.67.8]      $@ $1@${HUB}     user@local -> user@hub

Fortunately, the class configuration command provides an alternative. Use of a class list allows one rule to be written that does exactly the same thing as the three preceding rules:

>     R$-@$=w         $@ $1@${HUB}            user@local -> user@hub

Let's examine this new rule, then test it.

### 12.1.3 Class Macros in the LHS

The list of words in a class are referenced in the LHS of rules by prefixing the class name with the characters `$=`. For the class named `w` the expression in the LHS looks like this:

>     $=w

To understand how this expression is evaluated, we need to look at how the words in the class are stored. Each word that sendmail adds to the list (whether internally or from a `C` configuration command) is stored by sendmail in its symbol table. The symbol table holds all the words that appear in the configuration file. It holds the values of macro definitions and the symbolic names of delivery agents. It also holds the words that form a class list.

Next, we need to review how the workspace is tokenized and compared to the LHS of a rule. Just before the LHS is compared to the workspace, the workspace is tokenized. The address of a local user in the workspace might look like, and be tokenized like, the following:

>     you@here   becomes    you @ here

When the `$=w` expression appears in the LHS of a rule, it is compared to the workspace at the point that corresponds to the class's position in the LHS:

>     R$-@$=w         $@$1@${HUB}            user@local -> user@hub

>         here

In performing its minimum match of the workspace to the LHS, the matches are the following:

>     $-     match you
>     @      match @
>     $=w    does here match any token in class w?

When sendmail encounters a class-matching expression in the LHS, it looks up the corresponding token from the workspace in its symbol table. Here it looks up `here`. Because `here` is listed in the symbol table as belonging to the class `w`, sendmail finds a match. If there were no `here` in the symbol table, the match would fail. If there were one or more `here` entries in the symbol table, but none marked as belonging to class `w`, the match would also fail.

### 12.1.4 Class Macros in the RHS

When a match in the LHS allows the RHS to rewrite the workspace, the token matched by the `$=` prefix can be referenced with a `$``digit` positional operator. This differs from the `$` macro prefix that you learned about earlier. Macros are expanded when the configuration file is read (so `$``digit` doesn't work), but classes are given values only when a rule is matched (so `$``digit` does work).

We don't use the positional operator in the RHS because we don't care what word matched. Instead, we take the username from the LHS with `$1`, add an `@` character and the name of the forwarding hub machine (the `${HUB}`):

>     R$-@$=w         $@$1@${HUB}            user@local -> user@hub

>      1  2                $2 not needed

### 12.1.5 Testing Class

Modify the client.cf file by changing the `$w` in the second rule (of `SHubset`) into a `$=w` so that we can begin to test the class:

>     R$-@$=w         $@ $1@${HUB}            user@local -> user@hub

>          insert an =

Now run sendmail in rule-testing mode once again:

>     % ./sendmail -Cclient.cf -bt
>     ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
>     Enter <ruleset> <address>
>     >

First use the `$=` rule-testing command to list the contents of the class `w`:

>     > $=w
>     here.us.edu
>     here
>     [123.45.67.8]

Next, to test the new rule, specify rule set `Hubset` and give sendmail an address with a user, an `@`, and one of the local addresses shown for your machine:

>     > Hubset you@here.us.edu
>     rewrite: ruleset 199   input: you @ here . us . edu
>     rewrite: ruleset 199 returns: you @ mail . us . edu

This result is just what we expected. After all, the value in `${HUB}` is `mail.us.edu`. Now give sendmail another address, only this time give it one not in the list:

>     > Hubset you@localhost
>     rewrite: ruleset 199   input: you @ localhost
>     rewrite: ruleset 199 returns: you @ localhost

This time the address was not rewritten because `localhost` is not listed in the class `w`. But, in general, localhost is a legitimate name for the local machine, so let's add it temporarily by employing a new rule-testing command that you haven't seen before:

>     > .Cw localhost
>     > $=w
>     here.us.edu
>     localhost
>     here
>     [123.45.67.8]

The `.C` command (new with V8.7) tells sendmail to temporarily add a word to a class. In this case we added the word `localhost` to the class `w`. We then ran the `$=` rule-testing command to confirm that sendmail had actually added the word to the class.

Test the address localhost again:

>     > Hubset you@localhost
>     rewrite: ruleset 199   input: you @ localhost
>     rewrite: ruleset 199 returns: you @ mail . us . edu

This time, sendmail found `localhost` in the list and so rewrote the sender address.

### 12.1.6 Add Class w to client.cf

As we mentioned earlier, a host may be known by many names. Some of those names are found automatically by sendmail; others must be added by you. The name localhost is one possibility, and there may be others (such as printer1). You can add them to class `w` in rule-testing mode, but that is not a permanent solution (because they are forgotten as soon you leave rule-testing mode). Instead, you need to add them to your configuration file using the `C` configuration command.

Edit the client.cf file and add a new `C` configuration line to it:

>     V7
>     # Defined macros
>     D{REMOTE}mailhost              # The name of the mail hub
>     D{HUB}mail.us.edu              # Hub as known to the outside world

>     Cw localhost printer1          # My other names.                       new

Here we added `localhost` because it was missing and `printer1` to illustrate later points. Note that the name `printer1` is probably not appropriate for your site, but include it for now.

Run sendmail again in rule-testing mode:

>     % ./sendmail -Cclient.cf -bt
>     ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
>     Enter <ruleset> <address>
>     >

Use the `$=` rule-testing command again. Make sure the `C` line added the new names to the class `w`:

>     > $=w
>     here.us.edu
>     localhost
>     here
>     [123.45.67.8]
>     printer1

We can now test each name. Remember that the rule we are testing is this one:

>     R$-@$=w         $@ $1@${HUB}            user@local -> user@hub

We want to match any sender address that is a username, an `@`, and any of the names in the class `w`. We also want to rewrite each to appear as though the user is from the hub.

>     > Hubset you@here.us.edu
>     rewrite: ruleset 199   input: you @ here . us . edu
>     rewrite: ruleset 199 returns: you @ mail . us . edu
>     > Hubset you@localhost
>     rewrite: ruleset 199   input: you @ localhost
>     rewrite: ruleset 199 returns: you @ mail . us . edu
>     > Hubset you@here
>     rewrite: ruleset 199   input: you @ here
>     rewrite: ruleset 199 returns: you @ mail . us . edu
>     > Hubset you@[123.45.67.8]
>     rewrite: ruleset 199   input: you @ [ 123 . 45 . 67 . 8 ]
>     rewrite: ruleset 199 returns: you @ mail . us . edu
>     > Hubset you@printer1
>     rewrite: ruleset 199   input: you @ printer1
>     rewrite: ruleset 199 returns: you @ mail . us . edu
>     > Hubset you@printer1.us.edu
>     rewrite: ruleset 199   input: you @ printer1 . us . edu
>     rewrite: ruleset 199 returns: you @ printer1 . us . edu

All the names in class `w` match as they should. Notice that here appears in the list as both a short and a fully qualified name, but printer1 does not. When we tried printer1 in a fully qualified form (in the last three lines above), it failed. To make printer1 succeed, we could add its fully qualified form to the class `w`, but if we did that, we would have to do the same for localhost and any other names that we added. Instead, we will deal with the problem by adding a rule to the client.cf file.

### 12.1.7 Adding the Domain

There are three approaches to writing a rule that matches any of the local hostnames with a domain added:

1. Use a `$*` wildcard operator to match anything after the local hostname:

   >     R$-@$=w$*

   But this is unwise because it assumes that anything that follows the host is a domain, which might not always be true.

2. Use your domain name directly in the rule:

   >     R$-@$=w.us.edu

   Although this will work, it is better to use a macro in case you change your domain or use this file on a machine in another domain.

3. Use a macro that has your domain name as its value.

We will use the third approach because it is the cleanest. Begin by running sendmail with the `-d0.1` debugging switch again:

>     Version 8.8.4
>      Compiled with: LOG MATCHGECOS MIME7TO8 MIME8TO7 NAMED_BIND NDBM NETINET
>                     NETUNIX NIS SCANF XDEBUG

>     ============ SYSTEM IDENTITY (after readcf) ============
>           (short domain name) $w = here
>       (canonical domain name) $j = here.us.edu
>              (subdomain name) $m = us.edu
>                   (node name) $k = here
>     ========================================================

>     ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
>     Enter <ruleset> <address>
>     >

Note that the line that defines the "subdomain name" (the one with the `$m`) is the local domain that we need for our new rule. Add another rule to the `Hubset` rule set. This new rule looks for any local names (in `$=w`) that are followed by the local domain name defined for you by sendmail:

>     SHubset # Rewrite the sender for the hub
>     R$-             $@ $1@${HUB}            user -> user@hub
>     R$-@$=w         $@ $1@${HUB}            user@local -> user@hub
>     R$-@$=w.$m      $@ $1@${HUB}            user@local.domain -> user@hub    new

Now run sendmail once again and feed it the address that failed in the last test (but use your local domain):

>     % ./sendmail -Cclient.cf -bt
>     ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
>     Enter <ruleset> <address>
>     > Hubset you@printer1.us.edu
>     rewrite: ruleset 199   input: you @ printer1 . us . edu
>     rewrite: ruleset 199 returns: you @ mail . us . edu

As expected, the new rule in the `Hubset` rule set does its job. It finds that the `printer1.us.edu` part of the address matches the `$=w.$m` part of the rule and rewrites the workspace to make the sender's address appear as though it is from the hub.

11.8 Things to Try |  | 12.2 The File Form of Class